Introduction

  • Keep in mind the tradeoffs based on speed, explainability, simplicity & performance
  • Bike sharing data is found on Kaggle
  • Potential things to explore:
    • Duration of travel, departure and arrival position
    • Casual vs. registered rental wrt weather, time, etc.
    • Understand seasonality / time series analysis
    • Predict number of rentals on a day
    • Event and Anomaly Detection

Data

Sample

options(scipen=999)
seed = 2018
pkgs = c('data.table', 'magrittr', 'caret', 'glmnet', 'ranger', 'plotly', 'iml')
inst = lapply(pkgs, library, character.only=T, quietly=T); rm(pkgs, inst)
source('~/Documents/R/Utils/functions/summarize.R')
source('~/Documents/R/Utils/functions/one_hot.R')
source('~/Documents/R/Utils/functions/model_auc.R')
source('~/Documents/R/Utils/functions/feature_comparison.R')
source('~/Documents/R/Utils/functions/feature_distribution.R')
source('~/Documents/R/Utils/functions/helper.R')
day = fread("~/Rrrrr/projects/bike_sharing/day.csv") %>%
  .[, dteday := as.Date(dteday)]
hour = fread("~/Rrrrr/projects/bike_sharing/hour.csv") %>%
  .[, dteday := as.Date(dteday)]
head(day)

Summarize

The goal is to find variables with:

  • missing values
  • low variance
  • outliers – casual users shows the most diverse behavior

Day

sum.day = Summarize(day); DT::datatable(sum.day, options = list(dom = "tip"))

Hour

sum.hour = Summarize(hour); DT::datatable(sum.hour, options = list(dom = "tip"))

Learnings

  • Holiday is only marked for weekdays
day[,.N,.(weekday, workingday, holiday)]

Anomaly detection EDA

  • Given condition, actual rental is way higher than predicted rentals, especially casual renters on days of events
  • Time of rental – might be a peak at certain time
  • My guess is holiday is more likely to have events – only 21 holidays
  • Holiday & weekends people might use bikes on a longer stretch of time
  • Clustering on raw features & on shapley values

Feature Density

FeatureComparison(list(holiday = day[holiday==1, cnt], 
                       weekday = day[holiday==0 & workingday==1, cnt],
                       weekend = day[holiday==0 & workingday==0, cnt]),
                  vectorName = "Total rental", sample = F, numerical = T)

FeatureComparison(list(holiday = day[holiday==1, casual], 
                       weekday = day[holiday==0 & workingday==1, casual],
                       weekend = day[holiday==0 & workingday==0, casual]),
                  vectorName = "Casual rental", sample = F, numerical = T)

FeatureComparison(list(holiday = day[holiday==1, registered], 
                       weekday = day[holiday==0 & workingday==1, registered],
                       weekend = day[holiday==0 & workingday==0, registered]),
                  vectorName = "Registered rental", sample = T, numerical = T)

Feature Over Time

  • 2012-10-29 & 2012-10-30 why is it so low?
    • Hurricane Sandy
FeatureDistribution(list(weekday = day[holiday==0 & workingday==1],
                         weekend = day[holiday==0 & workingday==0],
                         holiday = day[holiday==1], overall = day),
                    featureName = "cnt", periodName = "dteday")

  • Casual users show the most diverse behaviors between holiday, weekends & weekdays
  • 2011-07-04 is very high – 4th of July!
FeatureDistribution(list(weekday = day[holiday==0 & workingday==1],
                         weekend = day[holiday==0 & workingday==0],
                         holiday = day[holiday==1], overall = day),
                    featureName = "casual", periodName = "dteday")

  • Looks like registered users like to use more during weekdays, which makes sense as the registered users use bikes as work commute transportation
FeatureDistribution(list(weekday = day[holiday==0 & workingday==1],
                         weekend = day[holiday==0 & workingday==0],
                         holiday = day[holiday==1], overall = day),
                    featureName = "registered", periodName = "dteday")

Weather

  • Casual users like to bike on cooler days during summer, and clearer days in spring & fall
FeatureDistribution(list(Clear = day[weathersit==1], 
                         Mist = day[weathersit==2],
                         Light = day[weathersit==3]),
                    featureName = "casual", periodName = "mnth")

  • Again, weather has less effect on registered users (similar to weekdays)
FeatureDistribution(list(Clear = day[weathersit==1], 
                         Mist = day[weathersit==2],
                         Light = day[weathersit==3]),
                    featureName = "registered", periodName = "mnth")

Day of week

  • Casual rental: Saturdays & Sundays are heavy days
FeatureComparison(list(S = day[weekday==0, casual], 
                       M = day[weekday==1, casual],
                       T = day[weekday==2, casual],
                       W = day[weekday==3, casual],
                       Th = day[weekday==4, casual],
                       F = day[weekday==5, casual],
                       St = day[weekday==6, casual]),
                  vectorName = "Casual rental", sample = F, numerical = T)

  • Registered rental: Wednesdays & Thursdays are heavy days
FeatureComparison(list(S = day[weekday==0, registered], 
                       M = day[weekday==1, registered],
                       T = day[weekday==2, registered],
                       W = day[weekday==3, registered],
                       Th = day[weekday==4, registered],
                       F = day[weekday==5, registered],
                       St = day[weekday==6, registered]),
                  vectorName = "Registered rental", sample = F, numerical = T)

Time

  • Starting from 0, every 15\(^\circ\) is one hour; so 120\(^\circ\) means 8am and 255\(^\circ\) means 5pm
  • How far the dot is from origin is the average number of users during that time

  • Casual users
    • Rentals on Saturday > Sunday > Weekdays
    • More rentals in the afternoon on weekends
    • Most weekday rental at ~5pm
  • Registered users
    • More rentals during weekdays > weekends, Saturday & Sunday uses are not very different
    • Peak hours are 8am, 5pm & 6pm during weekdays
time = hour[workingday==1, .(day = "Weekday", c = mean(casual), r = mean(registered)), hr] %>%
  rbind(hour[weekday==6, .(day = "Sat", c = mean(casual), r = mean(registered)), hr]) %>%
  rbind(hour[weekday==0, .(day = "Sun", c = mean(casual), r = mean(registered)), hr]) %>%
  .[, deg := hr*15] %>%
  .[, let := LETTERS[hr+1]]
# plot_ly(time, x=~hr, y=~cnt, color = ~day) 
p <- plot_ly(type = 'scatterpolar', mode = 'markers', 
             r = time[day=="Weekday"]$c,
             theta = time[day=="Weekday"]$deg,
             name = "Casual_Week", marker = list(opacity = 0.5)) %>%
  add_trace(type = 'scatterpolar', mode = 'markers', 
            r = time[day=="Sat"]$c,
            theta = time[day=="Sat"]$deg,
            name = "Casual_Sat", marker = list(opacity = 0.5)) %>%
  add_trace(type = 'scatterpolar', mode = 'markers', 
            r = time[day=="Sun"]$c,
            theta = time[day=="Sun"]$deg,
            name = "Casual_Sun", marker = list(opacity = 0.5)) %>%
  add_trace(type = 'scatterpolar', mode = 'markers', 
            r = time[day=="Weekday"]$r,
            theta = time[day=="Weekday"]$deg,
            name = "Reg_Week",
            marker = list(opacity = 0.5)) %>%
  add_trace(type = 'scatterpolar', mode = 'markers', 
            r = time[day=="Sat"]$r,
            theta = time[day=="Sat"]$deg,
            name = "Reg_Sat",
            marker = list(opacity = 0.5)) %>%
  add_trace(type = 'scatterpolar', mode = 'markers', 
            r = time[day=="Sun"]$r,
            theta = time[day=="Sun"]$deg,
            name = "Reg_Sun",
            marker = list(opacity = 0.5)) %>%
  layout(polar = list(radialaxis = list(angle = 0),
                      angularaxis = list(direction = 'clockwise', dtick = 15)))
p

Holiday

  • 21 Holidays – easy to spot days with events
day[,.N, .(holiday, workingday)]
day[holiday==1,.(dteday, yr, weekday, casual, registered, cnt)]

  • Time
hour[dteday=="2011-07-04",.(hr, weathersit, temp, atemp, hum, windspeed, casual, registered, cnt)]

Regression

Train-test split

  • Time split
cutoff = "2012-09-01"
# train-test split by time
data.train = hour[dteday<cutoff, -c("instant", "dteday", "casual", "registered")]
week.train = hour[dteday<cutoff] %>% .[, week := week(dteday)] %>%
  .[, -c("instant", "dteday", "casual", "registered")]
date.train = hour[dteday<cutoff] %>% .[, week := week(dteday)] %>%
  .[, date := mday(dteday)] %>%
  .[, -c("instant", "dteday", "casual", "registered")]
cas.train = hour[dteday<cutoff]$casual
reg.train = hour[dteday<cutoff]$registered
data.test = hour[dteday>=cutoff, -c("instant", "dteday", "casual", "registered")]
week.test = hour[dteday>=cutoff] %>% .[, week := week(dteday)] %>%
  .[, -c("instant", "dteday", "casual", "registered")]
date.test = hour[dteday>=cutoff] %>% .[, week := week(dteday)] %>%
  .[, date := mday(dteday)] %>%
  .[, -c("instant", "dteday", "casual", "registered")]
# check to make sure distribution is consistent
FeatureDistribution(list(Train = hour[dteday<cutoff], 
                         Test = hour[dteday>=cutoff]),
                    featureName = "cnt", periodName = "dteday")

One-hot-encoding

  • Season & weekday
hour.ohot = copy(hour)
OneHot(hour.ohot, "mnth")
OneHot(hour.ohot, "hr")
OneHot(hour.ohot, "season")
OneHot(hour.ohot, "weekday")
OneHot(hour.ohot, "weathersit")
ohot.train = hour.ohot[dteday<cutoff, -c("instant", "dteday", "casual", "registered")]
ohot.test = hour.ohot[dteday>=cutoff, -c("instant", "dteday", "casual", "registered")]

Additional work

  • Identify & remove outliers

Model build

  • Train with entire dataset
  • Train with events (0/1) column
  • Train separate models for registered vs. casual users
    • Exact
  • Evaluation metric determines how good a model is, for e.g. RMSE means predicted values are in a narrow band in comparison to MAE

GLM

# 10-fold CV
set.seed(seed)
glm.ctr <- trainControl(method = "repeatedcv", number = 10, repeats = 5)
# search grid
glm.grd <-  expand.grid(alpha = 0:10/10, lambda = 10^seq(3,-6))
# model                        
set.seed(seed)
glm.all <- train(x = ohot.train[,-"cnt"], y = ohot.train$cnt, 
                 method = "glmnet", trControl = glm.ctr, tuneGrid = glm.grd)
There were missing values in resampled performance measures.
plot(glm.all)


glm.all$bestTune

  • RMSE = 132.4112055
  • RMSE results in conservative predictions – predicted values are all < 600
pred.glm = data.table(actual = ohot.test$cnt, 
                      pred = predict(glm.all, newdata = ohot.test))
plot_ly(pred.glm, x = ~actual, y = ~pred) %>%
  add_markers() %>%
  add_lines(x=c(1,800), y=c(1,800), showlegend = F)

  • In comparison, RMSE on train = 95.1339412
  • GLM has a higher bias
pred.glm.train = data.table(actual = ohot.train$cnt, 
                            pred = predict(glm.all, newdata = ohot.train))
plot_ly(pred.glm.train, x = ~actual, y = ~pred) %>%
  add_markers() %>%
  add_lines(x=c(1,800), y=c(1,800), showlegend = F)

GLM(2models)

Casual model
# model                        
set.seed(seed)
glm.cas <- train(x = ohot.train[,-"cnt"], y = cas.train, 
                 method = "glmnet", trControl = glm.ctr, tuneGrid = glm.grd)
There were missing values in resampled performance measures.
plot(glm.cas)


glm.cas$bestTune

Registered model
set.seed(seed)
glm.reg <- train(x = ohot.train[,-"cnt"], y = reg.train, 
                 method = "glmnet", trControl = glm.ctr, tuneGrid = glm.grd)
There were missing values in resampled performance measures.
plot(glm.reg)


glm.reg$bestTune

Performance
glm.two = data.table(hour[dteday>=cutoff, .(actual = cnt, dteday)],
                      cas = predict(glm.cas, newdata = data.fe)[14492:17379],
                      reg = predict(glm.reg, newdata = data.fe)[14492:17379]) %>%
  .[, pred := cas + reg]
Error in cbind2(1, newx) %*% nbeta : 
  Cholmod error 'X and/or Y have wrong dimensions' at file ../MatrixOps/cholmod_sdmult.c, line 90

FeatureComparison(list(glm.two$actual, glm.two$pred))

  • Average hourly prediction
FeatureDistribution(list(Train = hour[dteday<cutoff,.(dteday, group = "train", value = cnt)],
                         Test = glm.two[,.(dteday, group = "actual", value = actual)],
                         Preds = glm.two[,.(dteday, group = "pred", value = pred)]), 
                    featureName = "value", periodName = "dteday")

XGB

  • nrounds (# Boosting Iterations), max_depth (Max Tree Depth), eta (Shrinkage), gamma (Minimum Loss Reduction), colsample_bytree (Subsample Ratio of Columns), min_child_weight (Minimum Sum of Instance Weight), subsample
# 10-fold CV
set.seed(seed)
xgb.ctr <- trainControl(method = "repeatedcv", number = 10, repeats = 1)
# search grid
# glm.grd <-  expand.grid(alpha = 0:10/10, lambda = 10^seq(3,-6))
# model                        
set.seed(seed)
xgb.all <- train(x = data.train[,-"cnt"], y = data.train$cnt, 
                 method = "xgbTree", trControl = xgb.ctr)
plot(xgb.all)


xgb.all$bestTune

Performance
  • RMSE = 79.3423604
pred.xgb = data.table(actual = data.test$cnt, 
                      pred = predict(xgb.all, newdata = data.test))
plot_ly(pred.xgb, x = ~actual, y = ~pred) %>%
  add_markers() %>%
  add_lines(x=c(1,800), y=c(1,800), showlegend = F)

  • In comparison, RMSE on train = 45.8525654
pred.xgb.train = data.table(actual = data.train$cnt, 
                            pred = predict(xgb.all, newdata = data.train))
plot_ly(pred.xgb.train, x = ~actual, y = ~pred) %>%
  add_markers() %>%
  add_lines(x=c(1,800), y=c(1,800), showlegend = F)

XGB(2models)

  • Slightly performance as the normal XGB model
Casual model
set.seed(seed)
xgb.cas <- train(x = data.train[,-"cnt"], y = cas.train, 
                 method = "xgbTree", trControl = xgb.ctr)
plot(xgb.cas)


xgb.cas$bestTune

Registered model
set.seed(seed)
xgb.reg <- train(x = data.train[,-"cnt"], y = reg.train, 
                 method = "xgbTree", trControl = xgb.ctr)
plot(xgb.reg)


xgb.reg$bestTune

Performance
  • RMSE = 77.6327841
pred.two = data.table(hour[dteday>=cutoff, .(actual = cnt, dteday)],
                      cas = predict(xgb.cas, newdata = data.test),
                      reg = predict(xgb.reg, newdata = data.test)) %>%
  .[, pred := cas + reg]
plot_ly(pred.two, x = ~actual, y = ~pred) %>%
  add_markers() %>%
  add_lines(x=c(1,800), y=c(1,800), showlegend = F)

FeatureComparison(list(pred.two$actual, pred.two$pred))

  • Average hourly prediction
FeatureDistribution(list(Train = hour[dteday<cutoff,.(dteday, group = "train", value = cnt)],
                         Test = pred.two[,.(dteday, group = "actual", value = actual)],
                         Preds = pred.two[,.(dteday, group = "pred", value = pred)]), 
                    featureName = "value", periodName = "dteday")

XGB(week)

# 10-fold CV
set.seed(seed)
xgb.ctr <- trainControl(method = "repeatedcv", number = 10, repeats = 1)
# search grid
# glm.grd <-  expand.grid(alpha = 0:10/10, lambda = 10^seq(3,-6))
# model                        
set.seed(seed)
xgb.week <- train(x = week.train[,-"cnt"], y = week.train$cnt, 
                 method = "xgbTree", trControl = xgb.ctr)
plot(xgb.week)


xgb.week$bestTune

  • RMSE = 78.897122
week.xgb = data.table(hour[dteday>=cutoff, .(actual = cnt, dteday)],
                      pred = predict(xgb.week, newdata = week.test))
plot_ly(week.xgb, x = ~actual, y = ~pred) %>%
  add_markers() %>%
  add_lines(x=c(1,800), y=c(1,800), showlegend = F)

  • Average hourly prediction
FeatureDistribution(list(Train = hour[dteday<cutoff,.(dteday, group = "train", value = cnt)],
                         Test = week.xgb[,.(dteday, group = "actual", value = actual)],
                         Preds = week.xgb[,.(dteday, group = "pred", value = pred)]), 
                    featureName = "value", periodName = "dteday")

XGB(date)

set.seed(seed)
xgb.date <- train(x = date.train[,-"cnt"], y = date.train$cnt, 
                 method = "xgbTree", trControl = xgb.ctr)
plot(xgb.date)


xgb.date$bestTune

  • RMSE = 77.7874697
date.xgb = data.table(hour[dteday>=cutoff, .(actual = cnt, dteday)],
                      pred = predict(xgb.date, newdata = date.test))
plot_ly(date.xgb, x = ~actual, y = ~pred) %>%
  add_markers() %>%
  add_lines(x=c(1,800), y=c(1,800), showlegend = F)

  • Average hourly prediction
FeatureDistribution(list(Train = hour[dteday<cutoff,.(dteday, group = "train", value = cnt)],
                         Test = date.xgb[,.(dteday, group = "actual", value = actual)],
                         Preds = date.xgb[,.(dteday, group = "pred", value = pred)]), 
                    featureName = "value", periodName = "dteday")

XGB(no mnth)

set.seed(seed)
xgb.mth <- train(x = data.train[,-c("mnth","cnt")], y = data.train$cnt, 
                 method = "xgbTree", trControl = xgb.ctr)
plot(xgb.mth)


xgb.mth$bestTune

Performance
  • RMSE = 79.588598
mth.xgb = data.table(hour[dteday>=cutoff, .(actual = cnt, dteday)],
                     pred = predict(xgb.mth, newdata = data.test))
plot_ly(mth.xgb, x = ~actual, y = ~pred) %>%
  add_markers() %>%
  add_lines(x=c(1,800), y=c(1,800), showlegend = F)

  • In comparison, RMSE on train = 46.5857025
mth.xgb.train = data.table(actual = data.train$cnt, 
                           pred = predict(xgb.mth, newdata = data.train))
plot_ly(mth.xgb.train, x = ~actual, y = ~pred) %>%
  add_markers() %>%
  add_lines(x=c(1,800), y=c(1,800), showlegend = F)

Performance

FeatureDistribution(list(Train = hour[dteday<cutoff,.(dteday, group = "train", value = cnt)],
                         Test = date.xgb[,.(dteday, group = "actual", value = actual)],
                         NoMth = mth.xgb[,.(dteday, group = "pred", value = pred)],
                         XGB2 = pred.two[,.(dteday, group = "pred", value = pred)],
                         Week = week.xgb[,.(dteday, group = "pred", value = pred)],
                         Date = date.xgb[,.(dteday, group = "pred", value = pred)]), 
                    featureName = "value", periodName = "dteday")

Explain

#calc.relimp

save.image(file = "bike.RData")
load("bike.RData")
LS0tCnRpdGxlOiAiQmlrZSBTaGFyaW5nIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIHRoZW1lOiBmbGF0bHkKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogeWVzCi0tLQoKIyMgSW50cm9kdWN0aW9uCgoqIEtlZXAgaW4gbWluZCB0aGUgdHJhZGVvZmZzIGJhc2VkIG9uIHNwZWVkLCBleHBsYWluYWJpbGl0eSwgc2ltcGxpY2l0eSAmIHBlcmZvcm1hbmNlCiogQmlrZSBzaGFyaW5nIGRhdGEgaXMgZm91bmQgb24gW0thZ2dsZV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9tYXJrbHZsL2Jpa2Utc2hhcmluZy1kYXRhc2V0L2hvbWUpCiogUG90ZW50aWFsIHRoaW5ncyB0byBleHBsb3JlOiAKICAgICsgRHVyYXRpb24gb2YgdHJhdmVsLCBkZXBhcnR1cmUgYW5kIGFycml2YWwgcG9zaXRpb24KICAgICsgQ2FzdWFsIHZzLiByZWdpc3RlcmVkIHJlbnRhbCB3cnQgd2VhdGhlciwgdGltZSwgZXRjLgogICAgKyBVbmRlcnN0YW5kIHNlYXNvbmFsaXR5IC8gdGltZSBzZXJpZXMgYW5hbHlzaXMKICAgICsgUHJlZGljdCBudW1iZXIgb2YgcmVudGFscyBvbiBhIGRheQogICAgKyBFdmVudCBhbmQgQW5vbWFseSBEZXRlY3Rpb24KICAgIAoqKioqKgojIyBEYXRhCiMjIyBTYW1wbGUKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm9wdGlvbnMoc2NpcGVuPTk5OSkKc2VlZCA9IDIwMTgKcGtncyA9IGMoJ2RhdGEudGFibGUnLCAnbWFncml0dHInLCAnY2FyZXQnLCAnZ2xtbmV0JywgJ3JhbmdlcicsICdwbG90bHknLCAnaW1sJykKaW5zdCA9IGxhcHBseShwa2dzLCBsaWJyYXJ5LCBjaGFyYWN0ZXIub25seT1ULCBxdWlldGx5PVQpOyBybShwa2dzLCBpbnN0KQpzb3VyY2UoJ34vRG9jdW1lbnRzL1IvVXRpbHMvZnVuY3Rpb25zL3N1bW1hcml6ZS5SJykKc291cmNlKCd+L0RvY3VtZW50cy9SL1V0aWxzL2Z1bmN0aW9ucy9vbmVfaG90LlInKQpzb3VyY2UoJ34vRG9jdW1lbnRzL1IvVXRpbHMvZnVuY3Rpb25zL21vZGVsX2F1Yy5SJykKc291cmNlKCd+L0RvY3VtZW50cy9SL1V0aWxzL2Z1bmN0aW9ucy9mZWF0dXJlX2NvbXBhcmlzb24uUicpCnNvdXJjZSgnfi9Eb2N1bWVudHMvUi9VdGlscy9mdW5jdGlvbnMvZmVhdHVyZV9kaXN0cmlidXRpb24uUicpCnNvdXJjZSgnfi9Eb2N1bWVudHMvUi9VdGlscy9mdW5jdGlvbnMvaGVscGVyLlInKQpkYXkgPSBmcmVhZCgifi9ScnJyci9wcm9qZWN0cy9iaWtlX3NoYXJpbmcvZGF5LmNzdiIpICU+JQogIC5bLCBkdGVkYXkgOj0gYXMuRGF0ZShkdGVkYXkpXQpob3VyID0gZnJlYWQoIn4vUnJycnIvcHJvamVjdHMvYmlrZV9zaGFyaW5nL2hvdXIuY3N2IikgJT4lCiAgLlssIGR0ZWRheSA6PSBhcy5EYXRlKGR0ZWRheSldCmhlYWQoZGF5KQpgYGAKCioqKioqCiMjIyBTdW1tYXJpemUgey50YWJzZXR9CgpUaGUgZ29hbCBpcyB0byBmaW5kIHZhcmlhYmxlcyB3aXRoOiAKCiogbWlzc2luZyB2YWx1ZXMKKiBsb3cgdmFyaWFuY2UKKiBvdXRsaWVycyAtLSBjYXN1YWwgdXNlcnMgc2hvd3MgdGhlIG1vc3QgZGl2ZXJzZSBiZWhhdmlvcgogICAKIyMjIyBEYXkgCmBgYHtyfQpzdW0uZGF5ID0gU3VtbWFyaXplKGRheSk7IERUOjpkYXRhdGFibGUoc3VtLmRheSwgb3B0aW9ucyA9IGxpc3QoZG9tID0gInRpcCIpKQpgYGAKCioqKioqCiMjIyMgSG91cgpgYGB7cn0Kc3VtLmhvdXIgPSBTdW1tYXJpemUoaG91cik7IERUOjpkYXRhdGFibGUoc3VtLmhvdXIsIG9wdGlvbnMgPSBsaXN0KGRvbSA9ICJ0aXAiKSkKYGBgCioqKioqCiMjIyBMZWFybmluZ3MKCiogSG9saWRheSBpcyBvbmx5IG1hcmtlZCBmb3Igd2Vla2RheXMKYGBge3J9CmRheVssLk4sLih3ZWVrZGF5LCB3b3JraW5nZGF5LCBob2xpZGF5KV0KYGBgCgoqKioqKgojIyBBbm9tYWx5IGRldGVjdGlvbiBFREEgey50YWJzZXR9CgoqIEdpdmVuIGNvbmRpdGlvbiwgYWN0dWFsIHJlbnRhbCBpcyB3YXkgaGlnaGVyIHRoYW4gcHJlZGljdGVkIHJlbnRhbHMsIGVzcGVjaWFsbHkgY2FzdWFsIHJlbnRlcnMgb24gZGF5cyBvZiBldmVudHMKKiBUaW1lIG9mIHJlbnRhbCAtLSBtaWdodCBiZSBhIHBlYWsgYXQgY2VydGFpbiB0aW1lCiogTXkgZ3Vlc3MgaXMgaG9saWRheSBpcyBtb3JlIGxpa2VseSB0byBoYXZlIGV2ZW50cyAtLSBvbmx5IDIxIGhvbGlkYXlzCiogSG9saWRheSAmIHdlZWtlbmRzIHBlb3BsZSBtaWdodCB1c2UgYmlrZXMgb24gYSBsb25nZXIgc3RyZXRjaCBvZiB0aW1lCiogQ2x1c3RlcmluZyBvbiByYXcgZmVhdHVyZXMgJiBvbiBzaGFwbGV5IHZhbHVlcwoKIyMjIEZlYXR1cmUgRGVuc2l0eQpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NH0KRmVhdHVyZUNvbXBhcmlzb24obGlzdChob2xpZGF5ID0gZGF5W2hvbGlkYXk9PTEsIGNudF0sIAogICAgICAgICAgICAgICAgICAgICAgIHdlZWtkYXkgPSBkYXlbaG9saWRheT09MCAmIHdvcmtpbmdkYXk9PTEsIGNudF0sCiAgICAgICAgICAgICAgICAgICAgICAgd2Vla2VuZCA9IGRheVtob2xpZGF5PT0wICYgd29ya2luZ2RheT09MCwgY250XSksCiAgICAgICAgICAgICAgICAgIHZlY3Rvck5hbWUgPSAiVG90YWwgcmVudGFsIiwgc2FtcGxlID0gRiwgbnVtZXJpY2FsID0gVCkKYGBgCgoqKioqKgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NH0KRmVhdHVyZUNvbXBhcmlzb24obGlzdChob2xpZGF5ID0gZGF5W2hvbGlkYXk9PTEsIGNhc3VhbF0sIAogICAgICAgICAgICAgICAgICAgICAgIHdlZWtkYXkgPSBkYXlbaG9saWRheT09MCAmIHdvcmtpbmdkYXk9PTEsIGNhc3VhbF0sCiAgICAgICAgICAgICAgICAgICAgICAgd2Vla2VuZCA9IGRheVtob2xpZGF5PT0wICYgd29ya2luZ2RheT09MCwgY2FzdWFsXSksCiAgICAgICAgICAgICAgICAgIHZlY3Rvck5hbWUgPSAiQ2FzdWFsIHJlbnRhbCIsIHNhbXBsZSA9IEYsIG51bWVyaWNhbCA9IFQpCmBgYAoKKioqKioKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CkZlYXR1cmVDb21wYXJpc29uKGxpc3QoaG9saWRheSA9IGRheVtob2xpZGF5PT0xLCByZWdpc3RlcmVkXSwgCiAgICAgICAgICAgICAgICAgICAgICAgd2Vla2RheSA9IGRheVtob2xpZGF5PT0wICYgd29ya2luZ2RheT09MSwgcmVnaXN0ZXJlZF0sCiAgICAgICAgICAgICAgICAgICAgICAgd2Vla2VuZCA9IGRheVtob2xpZGF5PT0wICYgd29ya2luZ2RheT09MCwgcmVnaXN0ZXJlZF0pLAogICAgICAgICAgICAgICAgICB2ZWN0b3JOYW1lID0gIlJlZ2lzdGVyZWQgcmVudGFsIiwgc2FtcGxlID0gVCwgbnVtZXJpY2FsID0gVCkKYGBgCgojIyMgRmVhdHVyZSBPdmVyIFRpbWUKCiogYDIwMTItMTAtMjlgICYgYDIwMTItMTAtMzBgIHdoeSBpcyBpdCBzbyBsb3c/CiAgICArIEh1cnJpY2FuZSBTYW5keQpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NH0KRmVhdHVyZURpc3RyaWJ1dGlvbihsaXN0KHdlZWtkYXkgPSBkYXlbaG9saWRheT09MCAmIHdvcmtpbmdkYXk9PTFdLAogICAgICAgICAgICAgICAgICAgICAgICAgd2Vla2VuZCA9IGRheVtob2xpZGF5PT0wICYgd29ya2luZ2RheT09MF0sCiAgICAgICAgICAgICAgICAgICAgICAgICBob2xpZGF5ID0gZGF5W2hvbGlkYXk9PTFdLCBvdmVyYWxsID0gZGF5KSwKICAgICAgICAgICAgICAgICAgICBmZWF0dXJlTmFtZSA9ICJjbnQiLCBwZXJpb2ROYW1lID0gImR0ZWRheSIpCmBgYAoKKioqKioKCiogQ2FzdWFsIHVzZXJzIHNob3cgdGhlIG1vc3QgZGl2ZXJzZSBiZWhhdmlvcnMgYmV0d2VlbiBob2xpZGF5LCB3ZWVrZW5kcyAmIHdlZWtkYXlzCiogYDIwMTEtMDctMDRgIGlzIHZlcnkgaGlnaCAtLSA0dGggb2YgSnVseSEKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CkZlYXR1cmVEaXN0cmlidXRpb24obGlzdCh3ZWVrZGF5ID0gZGF5W2hvbGlkYXk9PTAgJiB3b3JraW5nZGF5PT0xXSwKICAgICAgICAgICAgICAgICAgICAgICAgIHdlZWtlbmQgPSBkYXlbaG9saWRheT09MCAmIHdvcmtpbmdkYXk9PTBdLAogICAgICAgICAgICAgICAgICAgICAgICAgaG9saWRheSA9IGRheVtob2xpZGF5PT0xXSwgb3ZlcmFsbCA9IGRheSksCiAgICAgICAgICAgICAgICAgICAgZmVhdHVyZU5hbWUgPSAiY2FzdWFsIiwgcGVyaW9kTmFtZSA9ICJkdGVkYXkiKQpgYGAKCioqKioqCgoqIExvb2tzIGxpa2UgcmVnaXN0ZXJlZCB1c2VycyBsaWtlIHRvIHVzZSBtb3JlIGR1cmluZyB3ZWVrZGF5cywgd2hpY2ggbWFrZXMgc2Vuc2UgYXMgdGhlIHJlZ2lzdGVyZWQgdXNlcnMgdXNlIGJpa2VzIGFzIHdvcmsgY29tbXV0ZSB0cmFuc3BvcnRhdGlvbgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NH0KRmVhdHVyZURpc3RyaWJ1dGlvbihsaXN0KHdlZWtkYXkgPSBkYXlbaG9saWRheT09MCAmIHdvcmtpbmdkYXk9PTFdLAogICAgICAgICAgICAgICAgICAgICAgICAgd2Vla2VuZCA9IGRheVtob2xpZGF5PT0wICYgd29ya2luZ2RheT09MF0sCiAgICAgICAgICAgICAgICAgICAgICAgICBob2xpZGF5ID0gZGF5W2hvbGlkYXk9PTFdLCBvdmVyYWxsID0gZGF5KSwKICAgICAgICAgICAgICAgICAgICBmZWF0dXJlTmFtZSA9ICJyZWdpc3RlcmVkIiwgcGVyaW9kTmFtZSA9ICJkdGVkYXkiKQpgYGAKCioqKioqCiMjIyBXZWF0aGVyCgoqIENhc3VhbCB1c2VycyBsaWtlIHRvIGJpa2Ugb24gY29vbGVyIGRheXMgZHVyaW5nIHN1bW1lciwgYW5kIGNsZWFyZXIgZGF5cyBpbiBzcHJpbmcgJiBmYWxsCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQpGZWF0dXJlRGlzdHJpYnV0aW9uKGxpc3QoQ2xlYXIgPSBkYXlbd2VhdGhlcnNpdD09MV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgTWlzdCA9IGRheVt3ZWF0aGVyc2l0PT0yXSwKICAgICAgICAgICAgICAgICAgICAgICAgIExpZ2h0ID0gZGF5W3dlYXRoZXJzaXQ9PTNdKSwKICAgICAgICAgICAgICAgICAgICBmZWF0dXJlTmFtZSA9ICJjYXN1YWwiLCBwZXJpb2ROYW1lID0gIm1udGgiKQpgYGAKCioqKioqCgoqIEFnYWluLCB3ZWF0aGVyIGhhcyBsZXNzIGVmZmVjdCBvbiByZWdpc3RlcmVkIHVzZXJzIChzaW1pbGFyIHRvIHdlZWtkYXlzKQpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NH0KRmVhdHVyZURpc3RyaWJ1dGlvbihsaXN0KENsZWFyID0gZGF5W3dlYXRoZXJzaXQ9PTFdLCAKICAgICAgICAgICAgICAgICAgICAgICAgIE1pc3QgPSBkYXlbd2VhdGhlcnNpdD09Ml0sCiAgICAgICAgICAgICAgICAgICAgICAgICBMaWdodCA9IGRheVt3ZWF0aGVyc2l0PT0zXSksCiAgICAgICAgICAgICAgICAgICAgZmVhdHVyZU5hbWUgPSAicmVnaXN0ZXJlZCIsIHBlcmlvZE5hbWUgPSAibW50aCIpCmBgYAoKKioqKioKIyMjIERheSBvZiB3ZWVrCgoqIENhc3VhbCByZW50YWw6IFNhdHVyZGF5cyAmIFN1bmRheXMgYXJlIGhlYXZ5IGRheXMKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CkZlYXR1cmVDb21wYXJpc29uKGxpc3QoUyA9IGRheVt3ZWVrZGF5PT0wLCBjYXN1YWxdLCAKICAgICAgICAgICAgICAgICAgICAgICBNID0gZGF5W3dlZWtkYXk9PTEsIGNhc3VhbF0sCiAgICAgICAgICAgICAgICAgICAgICAgVCA9IGRheVt3ZWVrZGF5PT0yLCBjYXN1YWxdLAogICAgICAgICAgICAgICAgICAgICAgIFcgPSBkYXlbd2Vla2RheT09MywgY2FzdWFsXSwKICAgICAgICAgICAgICAgICAgICAgICBUaCA9IGRheVt3ZWVrZGF5PT00LCBjYXN1YWxdLAogICAgICAgICAgICAgICAgICAgICAgIEYgPSBkYXlbd2Vla2RheT09NSwgY2FzdWFsXSwKICAgICAgICAgICAgICAgICAgICAgICBTdCA9IGRheVt3ZWVrZGF5PT02LCBjYXN1YWxdKSwKICAgICAgICAgICAgICAgICAgdmVjdG9yTmFtZSA9ICJDYXN1YWwgcmVudGFsIiwgc2FtcGxlID0gRiwgbnVtZXJpY2FsID0gVCkKYGBgCgoqKioqKgoKKiBSZWdpc3RlcmVkIHJlbnRhbDogV2VkbmVzZGF5cyAmIFRodXJzZGF5cyBhcmUgaGVhdnkgZGF5cwpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NH0KRmVhdHVyZUNvbXBhcmlzb24obGlzdChTID0gZGF5W3dlZWtkYXk9PTAsIHJlZ2lzdGVyZWRdLCAKICAgICAgICAgICAgICAgICAgICAgICBNID0gZGF5W3dlZWtkYXk9PTEsIHJlZ2lzdGVyZWRdLAogICAgICAgICAgICAgICAgICAgICAgIFQgPSBkYXlbd2Vla2RheT09MiwgcmVnaXN0ZXJlZF0sCiAgICAgICAgICAgICAgICAgICAgICAgVyA9IGRheVt3ZWVrZGF5PT0zLCByZWdpc3RlcmVkXSwKICAgICAgICAgICAgICAgICAgICAgICBUaCA9IGRheVt3ZWVrZGF5PT00LCByZWdpc3RlcmVkXSwKICAgICAgICAgICAgICAgICAgICAgICBGID0gZGF5W3dlZWtkYXk9PTUsIHJlZ2lzdGVyZWRdLAogICAgICAgICAgICAgICAgICAgICAgIFN0ID0gZGF5W3dlZWtkYXk9PTYsIHJlZ2lzdGVyZWRdKSwKICAgICAgICAgICAgICAgICAgdmVjdG9yTmFtZSA9ICJSZWdpc3RlcmVkIHJlbnRhbCIsIHNhbXBsZSA9IEYsIG51bWVyaWNhbCA9IFQpCmBgYAoKKioqKioKCiMjIyBUaW1lIAoKKiBTdGFydGluZyBmcm9tIDAsIGV2ZXJ5IDE1JF5cY2lyYyQgaXMgb25lIGhvdXI7IHNvIDEyMCReXGNpcmMkIG1lYW5zIDhhbSBhbmQgMjU1JF5cY2lyYyQgbWVhbnMgNXBtCiogSG93IGZhciB0aGUgZG90IGlzIGZyb20gb3JpZ2luIGlzIHRoZSBhdmVyYWdlIG51bWJlciBvZiB1c2VycyBkdXJpbmcgdGhhdCB0aW1lCgoqIENhc3VhbCB1c2VycwogICAgKyBSZW50YWxzIG9uIFNhdHVyZGF5ID4gU3VuZGF5ID4gV2Vla2RheXMKICAgICsgTW9yZSByZW50YWxzIGluIHRoZSBhZnRlcm5vb24gb24gd2Vla2VuZHMKICAgICsgTW9zdCB3ZWVrZGF5IHJlbnRhbCBhdCB+NXBtCiogUmVnaXN0ZXJlZCB1c2VycwogICAgKyBNb3JlIHJlbnRhbHMgZHVyaW5nIHdlZWtkYXlzID4gd2Vla2VuZHMsIFNhdHVyZGF5ICYgU3VuZGF5IHVzZXMgYXJlIG5vdCB2ZXJ5IGRpZmZlcmVudAogICAgKyBQZWFrIGhvdXJzIGFyZSA4YW0sIDVwbSAmIDZwbSBkdXJpbmcgd2Vla2RheXMKYGBge3IsIGZpZy53aWR0aD03fQp0aW1lID0gaG91clt3b3JraW5nZGF5PT0xLCAuKGRheSA9ICJXZWVrZGF5IiwgYyA9IG1lYW4oY2FzdWFsKSwgciA9IG1lYW4ocmVnaXN0ZXJlZCkpLCBocl0gJT4lCiAgcmJpbmQoaG91clt3ZWVrZGF5PT02LCAuKGRheSA9ICJTYXQiLCBjID0gbWVhbihjYXN1YWwpLCByID0gbWVhbihyZWdpc3RlcmVkKSksIGhyXSkgJT4lCiAgcmJpbmQoaG91clt3ZWVrZGF5PT0wLCAuKGRheSA9ICJTdW4iLCBjID0gbWVhbihjYXN1YWwpLCByID0gbWVhbihyZWdpc3RlcmVkKSksIGhyXSkgJT4lCiAgLlssIGRlZyA6PSBocioxNV0gJT4lCiAgLlssIGxldCA6PSBMRVRURVJTW2hyKzFdXQojIHBsb3RfbHkodGltZSwgeD1+aHIsIHk9fmNudCwgY29sb3IgPSB+ZGF5KSAKcCA8LSBwbG90X2x5KHR5cGUgPSAnc2NhdHRlcnBvbGFyJywgbW9kZSA9ICdtYXJrZXJzJywgCiAgICAgICAgICAgICByID0gdGltZVtkYXk9PSJXZWVrZGF5Il0kYywKICAgICAgICAgICAgIHRoZXRhID0gdGltZVtkYXk9PSJXZWVrZGF5Il0kZGVnLAogICAgICAgICAgICAgbmFtZSA9ICJDYXN1YWxfV2VlayIsIG1hcmtlciA9IGxpc3Qob3BhY2l0eSA9IDAuNSkpICU+JQogIGFkZF90cmFjZSh0eXBlID0gJ3NjYXR0ZXJwb2xhcicsIG1vZGUgPSAnbWFya2VycycsIAogICAgICAgICAgICByID0gdGltZVtkYXk9PSJTYXQiXSRjLAogICAgICAgICAgICB0aGV0YSA9IHRpbWVbZGF5PT0iU2F0Il0kZGVnLAogICAgICAgICAgICBuYW1lID0gIkNhc3VhbF9TYXQiLCBtYXJrZXIgPSBsaXN0KG9wYWNpdHkgPSAwLjUpKSAlPiUKICBhZGRfdHJhY2UodHlwZSA9ICdzY2F0dGVycG9sYXInLCBtb2RlID0gJ21hcmtlcnMnLCAKICAgICAgICAgICAgciA9IHRpbWVbZGF5PT0iU3VuIl0kYywKICAgICAgICAgICAgdGhldGEgPSB0aW1lW2RheT09IlN1biJdJGRlZywKICAgICAgICAgICAgbmFtZSA9ICJDYXN1YWxfU3VuIiwgbWFya2VyID0gbGlzdChvcGFjaXR5ID0gMC41KSkgJT4lCiAgYWRkX3RyYWNlKHR5cGUgPSAnc2NhdHRlcnBvbGFyJywgbW9kZSA9ICdtYXJrZXJzJywgCiAgICAgICAgICAgIHIgPSB0aW1lW2RheT09IldlZWtkYXkiXSRyLAogICAgICAgICAgICB0aGV0YSA9IHRpbWVbZGF5PT0iV2Vla2RheSJdJGRlZywKICAgICAgICAgICAgbmFtZSA9ICJSZWdfV2VlayIsCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3Qob3BhY2l0eSA9IDAuNSkpICU+JQogIGFkZF90cmFjZSh0eXBlID0gJ3NjYXR0ZXJwb2xhcicsIG1vZGUgPSAnbWFya2VycycsIAogICAgICAgICAgICByID0gdGltZVtkYXk9PSJTYXQiXSRyLAogICAgICAgICAgICB0aGV0YSA9IHRpbWVbZGF5PT0iU2F0Il0kZGVnLAogICAgICAgICAgICBuYW1lID0gIlJlZ19TYXQiLAogICAgICAgICAgICBtYXJrZXIgPSBsaXN0KG9wYWNpdHkgPSAwLjUpKSAlPiUKICBhZGRfdHJhY2UodHlwZSA9ICdzY2F0dGVycG9sYXInLCBtb2RlID0gJ21hcmtlcnMnLCAKICAgICAgICAgICAgciA9IHRpbWVbZGF5PT0iU3VuIl0kciwKICAgICAgICAgICAgdGhldGEgPSB0aW1lW2RheT09IlN1biJdJGRlZywKICAgICAgICAgICAgbmFtZSA9ICJSZWdfU3VuIiwKICAgICAgICAgICAgbWFya2VyID0gbGlzdChvcGFjaXR5ID0gMC41KSkgJT4lCiAgbGF5b3V0KHBvbGFyID0gbGlzdChyYWRpYWxheGlzID0gbGlzdChhbmdsZSA9IDApLAogICAgICAgICAgICAgICAgICAgICAgYW5ndWxhcmF4aXMgPSBsaXN0KGRpcmVjdGlvbiA9ICdjbG9ja3dpc2UnLCBkdGljayA9IDE1KSkpCgpwCmBgYAoKKioqKioKIyMjIEhvbGlkYXkKCiogMjEgSG9saWRheXMgLS0gZWFzeSB0byBzcG90IGRheXMgd2l0aCBldmVudHMKYGBge3J9CmRheVssLk4sIC4oaG9saWRheSwgd29ya2luZ2RheSldCmBgYAoKYGBge3J9CmRheVtob2xpZGF5PT0xLC4oZHRlZGF5LCB5ciwgd2Vla2RheSwgY2FzdWFsLCByZWdpc3RlcmVkLCBjbnQpXQpgYGAKCioqKioqCiogVGltZQpgYGB7cn0KaG91cltkdGVkYXk9PSIyMDExLTA3LTA0IiwuKGhyLCB3ZWF0aGVyc2l0LCB0ZW1wLCBhdGVtcCwgaHVtLCB3aW5kc3BlZWQsIGNhc3VhbCwgcmVnaXN0ZXJlZCwgY250KV0KYGBgCgoqKioqKgojIyBSZWdyZXNzaW9uCiMjIyBUcmFpbi10ZXN0IHNwbGl0CgoqIFRpbWUgc3BsaXQKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CmN1dG9mZiA9ICIyMDEyLTA5LTAxIgojIHRyYWluLXRlc3Qgc3BsaXQgYnkgdGltZQpkYXRhLnRyYWluID0gaG91cltkdGVkYXk8Y3V0b2ZmLCAtYygiaW5zdGFudCIsICJkdGVkYXkiLCAiY2FzdWFsIiwgInJlZ2lzdGVyZWQiKV0Kd2Vlay50cmFpbiA9IGhvdXJbZHRlZGF5PGN1dG9mZl0gJT4lIC5bLCB3ZWVrIDo9IHdlZWsoZHRlZGF5KV0gJT4lCiAgLlssIC1jKCJpbnN0YW50IiwgImR0ZWRheSIsICJjYXN1YWwiLCAicmVnaXN0ZXJlZCIpXQpkYXRlLnRyYWluID0gaG91cltkdGVkYXk8Y3V0b2ZmXSAlPiUgLlssIHdlZWsgOj0gd2VlayhkdGVkYXkpXSAlPiUKICAuWywgZGF0ZSA6PSBtZGF5KGR0ZWRheSldICU+JQogIC5bLCAtYygiaW5zdGFudCIsICJkdGVkYXkiLCAiY2FzdWFsIiwgInJlZ2lzdGVyZWQiKV0KY2FzLnRyYWluID0gaG91cltkdGVkYXk8Y3V0b2ZmXSRjYXN1YWwKcmVnLnRyYWluID0gaG91cltkdGVkYXk8Y3V0b2ZmXSRyZWdpc3RlcmVkCmRhdGEudGVzdCA9IGhvdXJbZHRlZGF5Pj1jdXRvZmYsIC1jKCJpbnN0YW50IiwgImR0ZWRheSIsICJjYXN1YWwiLCAicmVnaXN0ZXJlZCIpXQp3ZWVrLnRlc3QgPSBob3VyW2R0ZWRheT49Y3V0b2ZmXSAlPiUgLlssIHdlZWsgOj0gd2VlayhkdGVkYXkpXSAlPiUKICAuWywgLWMoImluc3RhbnQiLCAiZHRlZGF5IiwgImNhc3VhbCIsICJyZWdpc3RlcmVkIildCmRhdGUudGVzdCA9IGhvdXJbZHRlZGF5Pj1jdXRvZmZdICU+JSAuWywgd2VlayA6PSB3ZWVrKGR0ZWRheSldICU+JQogIC5bLCBkYXRlIDo9IG1kYXkoZHRlZGF5KV0gJT4lCiAgLlssIC1jKCJpbnN0YW50IiwgImR0ZWRheSIsICJjYXN1YWwiLCAicmVnaXN0ZXJlZCIpXQojIGNoZWNrIHRvIG1ha2Ugc3VyZSBkaXN0cmlidXRpb24gaXMgY29uc2lzdGVudApGZWF0dXJlRGlzdHJpYnV0aW9uKGxpc3QoVHJhaW4gPSBob3VyW2R0ZWRheTxjdXRvZmZdLCAKICAgICAgICAgICAgICAgICAgICAgICAgIFRlc3QgPSBob3VyW2R0ZWRheT49Y3V0b2ZmXSksCiAgICAgICAgICAgICAgICAgICAgZmVhdHVyZU5hbWUgPSAiY250IiwgcGVyaW9kTmFtZSA9ICJkdGVkYXkiKQpgYGAKCioqKioqCiMjIyBPbmUtaG90LWVuY29kaW5nCgoqIFNlYXNvbiAmIHdlZWtkYXkKYGBge3J9CmhvdXIub2hvdCA9IGNvcHkoaG91cikKT25lSG90KGhvdXIub2hvdCwgIm1udGgiKQpPbmVIb3QoaG91ci5vaG90LCAiaHIiKQpPbmVIb3QoaG91ci5vaG90LCAic2Vhc29uIikKT25lSG90KGhvdXIub2hvdCwgIndlZWtkYXkiKQpPbmVIb3QoaG91ci5vaG90LCAid2VhdGhlcnNpdCIpCm9ob3QudHJhaW4gPSBob3VyLm9ob3RbZHRlZGF5PGN1dG9mZiwgLWMoImluc3RhbnQiLCAiZHRlZGF5IiwgImNhc3VhbCIsICJyZWdpc3RlcmVkIildCm9ob3QudGVzdCA9IGhvdXIub2hvdFtkdGVkYXk+PWN1dG9mZiwgLWMoImluc3RhbnQiLCAiZHRlZGF5IiwgImNhc3VhbCIsICJyZWdpc3RlcmVkIildCmBgYAoKKioqKioKIyMjIEFkZGl0aW9uYWwgd29yawoKKiBJZGVudGlmeSAmIHJlbW92ZSBvdXRsaWVycwpgYGB7cn0KCmBgYAoKKioqKioKIyMjIE1vZGVsIGJ1aWxkIHsudGFic2V0fQoKKiBUcmFpbiB3aXRoIGVudGlyZSBkYXRhc2V0CiogVHJhaW4gd2l0aCBldmVudHMgKGAwYC9gMWApIGNvbHVtbgoqIFRyYWluIHNlcGFyYXRlIG1vZGVscyBmb3IgcmVnaXN0ZXJlZCB2cy4gY2FzdWFsIHVzZXJzCiAgICArIEV4YWN0CiogRXZhbHVhdGlvbiBtZXRyaWMgZGV0ZXJtaW5lcyBob3cgZ29vZCBhIG1vZGVsIGlzLCBmb3IgZS5nLiBSTVNFIG1lYW5zIHByZWRpY3RlZCB2YWx1ZXMgYXJlIGluIGEgbmFycm93IGJhbmQgaW4gY29tcGFyaXNvbiB0byBNQUUKCiMjIyMgR0xNCmBgYHtyfQojIDEwLWZvbGQgQ1YKc2V0LnNlZWQoc2VlZCkKZ2xtLmN0ciA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gInJlcGVhdGVkY3YiLCBudW1iZXIgPSAxMCwgcmVwZWF0cyA9IDUpCiMgc2VhcmNoIGdyaWQKZ2xtLmdyZCA8LSAgZXhwYW5kLmdyaWQoYWxwaGEgPSAwOjEwLzEwLCBsYW1iZGEgPSAxMF5zZXEoMywtNikpCiMgbW9kZWwgICAgICAgICAgICAgICAgICAgICAgICAKc2V0LnNlZWQoc2VlZCkKZ2xtLmFsbCA8LSB0cmFpbih4ID0gb2hvdC50cmFpblssLSJjbnQiXSwgeSA9IG9ob3QudHJhaW4kY250LCAKICAgICAgICAgICAgICAgICBtZXRob2QgPSAiZ2xtbmV0IiwgdHJDb250cm9sID0gZ2xtLmN0ciwgdHVuZUdyaWQgPSBnbG0uZ3JkKQpwbG90KGdsbS5hbGwpCmBgYAoKKioqKioKYGBge3J9CmdsbS5hbGwkYmVzdFR1bmUKYGBgCgoqKioqKgoKKiBSTVNFID0gYHIgcHJlZC5nbG1bLCBtZWFuKChhY3R1YWwgLSBwcmVkKV4yKV0gJT4lIHNxcnRgCiogUk1TRSByZXN1bHRzIGluIGNvbnNlcnZhdGl2ZSBwcmVkaWN0aW9ucyAtLSBwcmVkaWN0ZWQgdmFsdWVzIGFyZSBhbGwgPCA2MDAKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CnByZWQuZ2xtID0gZGF0YS50YWJsZShhY3R1YWwgPSBvaG90LnRlc3QkY250LCAKICAgICAgICAgICAgICAgICAgICAgIHByZWQgPSBwcmVkaWN0KGdsbS5hbGwsIG5ld2RhdGEgPSBvaG90LnRlc3QpKQpwbG90X2x5KHByZWQuZ2xtLCB4ID0gfmFjdHVhbCwgeSA9IH5wcmVkKSAlPiUKICBhZGRfbWFya2VycygpICU+JQogIGFkZF9saW5lcyh4PWMoMSw4MDApLCB5PWMoMSw4MDApLCBzaG93bGVnZW5kID0gRikKYGBgCgoqKioqKgoKKiBJbiBjb21wYXJpc29uLCBSTVNFIG9uIHRyYWluID0gYHIgcHJlZC5nbG0udHJhaW5bLCBtZWFuKChhY3R1YWwgLSBwcmVkKV4yKV0gJT4lIHNxcnRgCiogR0xNIGhhcyBhIGhpZ2hlciBiaWFzCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQpwcmVkLmdsbS50cmFpbiA9IGRhdGEudGFibGUoYWN0dWFsID0gb2hvdC50cmFpbiRjbnQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlZCA9IHByZWRpY3QoZ2xtLmFsbCwgbmV3ZGF0YSA9IG9ob3QudHJhaW4pKQpwbG90X2x5KHByZWQuZ2xtLnRyYWluLCB4ID0gfmFjdHVhbCwgeSA9IH5wcmVkKSAlPiUKICBhZGRfbWFya2VycygpICU+JQogIGFkZF9saW5lcyh4PWMoMSw4MDApLCB5PWMoMSw4MDApLCBzaG93bGVnZW5kID0gRikKYGBgCgoqKioqKgojIyMjIEdMTSgybW9kZWxzKSB7LnRhYnNldH0KIyMjIyMgQ2FzdWFsIG1vZGVsCmBgYHtyfQojIG1vZGVsICAgICAgICAgICAgICAgICAgICAgICAgCnNldC5zZWVkKHNlZWQpCmdsbS5jYXMgPC0gdHJhaW4oeCA9IG9ob3QudHJhaW5bLC0iY250Il0sIHkgPSBjYXMudHJhaW4sIAogICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJnbG1uZXQiLCB0ckNvbnRyb2wgPSBnbG0uY3RyLCB0dW5lR3JpZCA9IGdsbS5ncmQpCnBsb3QoZ2xtLmNhcykKYGBgCgoqKioqKgpgYGB7cn0KZ2xtLmNhcyRiZXN0VHVuZQpgYGAKCioqKioqCiMjIyMjIFJlZ2lzdGVyZWQgbW9kZWwKYGBge3J9CnNldC5zZWVkKHNlZWQpCmdsbS5yZWcgPC0gdHJhaW4oeCA9IG9ob3QudHJhaW5bLC0iY250Il0sIHkgPSByZWcudHJhaW4sIAogICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJnbG1uZXQiLCB0ckNvbnRyb2wgPSBnbG0uY3RyLCB0dW5lR3JpZCA9IGdsbS5ncmQpCnBsb3QoZ2xtLnJlZykKYGBgCgoqKioqKgpgYGB7cn0KZ2xtLnJlZyRiZXN0VHVuZQpgYGAKCioqKioqCiMjIyMjIFBlcmZvcm1hbmNlCgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NH0KIyAqIFJNU0UgPSBgciBnbG0udHdvWywgbWVhbigoYWN0dWFsIC0gcHJlZCleMildICU+JSBzcXJ0YApnbG0udHdvID0gZGF0YS50YWJsZShob3VyW2R0ZWRheT49Y3V0b2ZmLCAuKGFjdHVhbCA9IGNudCwgZHRlZGF5KV0sCiAgICAgICAgICAgICAgICAgICAgICBjYXMgPSBwcmVkaWN0KGdsbS5jYXMsIG5ld2RhdGEgPSBob3VyKVsxNDQ5MjoxNzM3OV0sCiAgICAgICAgICAgICAgICAgICAgICByZWcgPSBwcmVkaWN0KGdsbS5yZWcsIG5ld2RhdGEgPSBob3VyKVsxNDQ5MjoxNzM3OV0pICU+JQogIC5bLCBwcmVkIDo9IGNhcyArIHJlZ10KcGxvdF9seShnbG0udHdvLCB4ID0gfmFjdHVhbCwgeSA9IH5wcmVkKSAlPiUKICBhZGRfbWFya2VycygpICU+JQogIGFkZF9saW5lcyh4PWMoMSw4MDApLCB5PWMoMSw4MDApLCBzaG93bGVnZW5kID0gRikKYGBgCgoqKioqKgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NH0KRmVhdHVyZUNvbXBhcmlzb24obGlzdChnbG0udHdvJGFjdHVhbCwgZ2xtLnR3byRwcmVkKSkKYGBgCgoqKioqKgoKKiBBdmVyYWdlIGhvdXJseSBwcmVkaWN0aW9uCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQpGZWF0dXJlRGlzdHJpYnV0aW9uKGxpc3QoVHJhaW4gPSBob3VyW2R0ZWRheTxjdXRvZmYsLihkdGVkYXksIGdyb3VwID0gInRyYWluIiwgdmFsdWUgPSBjbnQpXSwKICAgICAgICAgICAgICAgICAgICAgICAgIFRlc3QgPSBnbG0udHdvWywuKGR0ZWRheSwgZ3JvdXAgPSAiYWN0dWFsIiwgdmFsdWUgPSBhY3R1YWwpXSwKICAgICAgICAgICAgICAgICAgICAgICAgIFByZWRzID0gZ2xtLnR3b1ssLihkdGVkYXksIGdyb3VwID0gInByZWQiLCB2YWx1ZSA9IHByZWQpXSksIAogICAgICAgICAgICAgICAgICAgIGZlYXR1cmVOYW1lID0gInZhbHVlIiwgcGVyaW9kTmFtZSA9ICJkdGVkYXkiKQpgYGAKCioqKioqCiMjIyMgWEdCCgoqIGBucm91bmRzICgjIEJvb3N0aW5nIEl0ZXJhdGlvbnMpYCwgYG1heF9kZXB0aCAoTWF4IFRyZWUgRGVwdGgpYCwgYGV0YSAoU2hyaW5rYWdlKWAsIGBnYW1tYSAoTWluaW11bSBMb3NzIFJlZHVjdGlvbilgLCBgY29sc2FtcGxlX2J5dHJlZSAoU3Vic2FtcGxlIFJhdGlvIG9mIENvbHVtbnMpYCwgYG1pbl9jaGlsZF93ZWlnaHQgKE1pbmltdW0gU3VtIG9mIEluc3RhbmNlIFdlaWdodClgLCBgc3Vic2FtcGxlYApgYGB7cn0KIyAxMC1mb2xkIENWCnNldC5zZWVkKHNlZWQpCnhnYi5jdHIgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgbnVtYmVyID0gMTAsIHJlcGVhdHMgPSAxKQojIHNlYXJjaCBncmlkCiMgZ2xtLmdyZCA8LSAgZXhwYW5kLmdyaWQoYWxwaGEgPSAwOjEwLzEwLCBsYW1iZGEgPSAxMF5zZXEoMywtNikpCiMgbW9kZWwgICAgICAgICAgICAgICAgICAgICAgICAKc2V0LnNlZWQoc2VlZCkKeGdiLmFsbCA8LSB0cmFpbih4ID0gZGF0YS50cmFpblssLSJjbnQiXSwgeSA9IGRhdGEudHJhaW4kY250LCAKICAgICAgICAgICAgICAgICBtZXRob2QgPSAieGdiVHJlZSIsIHRyQ29udHJvbCA9IHhnYi5jdHIpCnBsb3QoeGdiLmFsbCkKYGBgCgoqKioqKgpgYGB7cn0KeGdiLmFsbCRiZXN0VHVuZQpgYGAKCioqKioqCiMjIyMjIFBlcmZvcm1hbmNlCgoqIFJNU0UgPSBgciBwcmVkLnhnYlssIG1lYW4oKGFjdHVhbCAtIHByZWQpXjIpXSAlPiUgc3FydGAKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CnByZWQueGdiID0gZGF0YS50YWJsZShhY3R1YWwgPSBkYXRhLnRlc3QkY250LCAKICAgICAgICAgICAgICAgICAgICAgIHByZWQgPSBwcmVkaWN0KHhnYi5hbGwsIG5ld2RhdGEgPSBkYXRhLnRlc3QpKQpwbG90X2x5KHByZWQueGdiLCB4ID0gfmFjdHVhbCwgeSA9IH5wcmVkKSAlPiUKICBhZGRfbWFya2VycygpICU+JQogIGFkZF9saW5lcyh4PWMoMSw4MDApLCB5PWMoMSw4MDApLCBzaG93bGVnZW5kID0gRikKYGBgCgoqKioqKgoqIEluIGNvbXBhcmlzb24sIFJNU0Ugb24gdHJhaW4gPSBgciBwcmVkLnhnYi50cmFpblssIG1lYW4oKGFjdHVhbCAtIHByZWQpXjIpXSAlPiUgc3FydGAKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CnByZWQueGdiLnRyYWluID0gZGF0YS50YWJsZShhY3R1YWwgPSBkYXRhLnRyYWluJGNudCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVkID0gcHJlZGljdCh4Z2IuYWxsLCBuZXdkYXRhID0gZGF0YS50cmFpbikpCnBsb3RfbHkocHJlZC54Z2IudHJhaW4sIHggPSB+YWN0dWFsLCB5ID0gfnByZWQpICU+JQogIGFkZF9tYXJrZXJzKCkgJT4lCiAgYWRkX2xpbmVzKHg9YygxLDgwMCksIHk9YygxLDgwMCksIHNob3dsZWdlbmQgPSBGKQpgYGAKCioqKioqCiMjIyMgWEdCKDJtb2RlbHMpIHsudGFic2V0fQoKKiBTbGlnaHRseSBwZXJmb3JtYW5jZSBhcyB0aGUgbm9ybWFsIFhHQiBtb2RlbAogCiMjIyMjIENhc3VhbCBtb2RlbApgYGB7cn0Kc2V0LnNlZWQoc2VlZCkKeGdiLmNhcyA8LSB0cmFpbih4ID0gZGF0YS50cmFpblssLSJjbnQiXSwgeSA9IGNhcy50cmFpbiwgCiAgICAgICAgICAgICAgICAgbWV0aG9kID0gInhnYlRyZWUiLCB0ckNvbnRyb2wgPSB4Z2IuY3RyKQpwbG90KHhnYi5jYXMpCmBgYAoKKioqKioKYGBge3J9CnhnYi5jYXMkYmVzdFR1bmUKYGBgCgoqKioqKgojIyMjIyBSZWdpc3RlcmVkIG1vZGVsCmBgYHtyfQpzZXQuc2VlZChzZWVkKQp4Z2IucmVnIDwtIHRyYWluKHggPSBkYXRhLnRyYWluWywtImNudCJdLCB5ID0gcmVnLnRyYWluLCAKICAgICAgICAgICAgICAgICBtZXRob2QgPSAieGdiVHJlZSIsIHRyQ29udHJvbCA9IHhnYi5jdHIpCnBsb3QoeGdiLnJlZykKYGBgCgoqKioqKgpgYGB7cn0KeGdiLnJlZyRiZXN0VHVuZQpgYGAKCioqKioqCiMjIyMjIFBlcmZvcm1hbmNlCgoqIFJNU0UgPSBgciBwcmVkLnR3b1ssIG1lYW4oKGFjdHVhbCAtIHByZWQpXjIpXSAlPiUgc3FydGAKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CnByZWQudHdvID0gZGF0YS50YWJsZShob3VyW2R0ZWRheT49Y3V0b2ZmLCAuKGFjdHVhbCA9IGNudCwgZHRlZGF5KV0sCiAgICAgICAgICAgICAgICAgICAgICBjYXMgPSBwcmVkaWN0KHhnYi5jYXMsIG5ld2RhdGEgPSBkYXRhLnRlc3QpLAogICAgICAgICAgICAgICAgICAgICAgcmVnID0gcHJlZGljdCh4Z2IucmVnLCBuZXdkYXRhID0gZGF0YS50ZXN0KSkgJT4lCiAgLlssIHByZWQgOj0gY2FzICsgcmVnXQpwbG90X2x5KHByZWQudHdvLCB4ID0gfmFjdHVhbCwgeSA9IH5wcmVkKSAlPiUKICBhZGRfbWFya2VycygpICU+JQogIGFkZF9saW5lcyh4PWMoMSw4MDApLCB5PWMoMSw4MDApLCBzaG93bGVnZW5kID0gRikKYGBgCgoqKioqKgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NH0KRmVhdHVyZUNvbXBhcmlzb24obGlzdChwcmVkLnR3byRhY3R1YWwsIHByZWQudHdvJHByZWQpKQpgYGAKCioqKioqCgoqIEF2ZXJhZ2UgaG91cmx5IHByZWRpY3Rpb24KYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CkZlYXR1cmVEaXN0cmlidXRpb24obGlzdChUcmFpbiA9IGhvdXJbZHRlZGF5PGN1dG9mZiwuKGR0ZWRheSwgZ3JvdXAgPSAidHJhaW4iLCB2YWx1ZSA9IGNudCldLAogICAgICAgICAgICAgICAgICAgICAgICAgVGVzdCA9IHByZWQudHdvWywuKGR0ZWRheSwgZ3JvdXAgPSAiYWN0dWFsIiwgdmFsdWUgPSBhY3R1YWwpXSwKICAgICAgICAgICAgICAgICAgICAgICAgIFByZWRzID0gcHJlZC50d29bLC4oZHRlZGF5LCBncm91cCA9ICJwcmVkIiwgdmFsdWUgPSBwcmVkKV0pLCAKICAgICAgICAgICAgICAgICAgICBmZWF0dXJlTmFtZSA9ICJ2YWx1ZSIsIHBlcmlvZE5hbWUgPSAiZHRlZGF5IikKYGBgCgoqKioqKgojIyMjIFhHQih3ZWVrKQpgYGB7cn0KIyAxMC1mb2xkIENWCnNldC5zZWVkKHNlZWQpCnhnYi5jdHIgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgbnVtYmVyID0gMTAsIHJlcGVhdHMgPSAxKQojIHNlYXJjaCBncmlkCiMgZ2xtLmdyZCA8LSAgZXhwYW5kLmdyaWQoYWxwaGEgPSAwOjEwLzEwLCBsYW1iZGEgPSAxMF5zZXEoMywtNikpCiMgbW9kZWwgICAgICAgICAgICAgICAgICAgICAgICAKc2V0LnNlZWQoc2VlZCkKeGdiLndlZWsgPC0gdHJhaW4oeCA9IHdlZWsudHJhaW5bLC0iY250Il0sIHkgPSB3ZWVrLnRyYWluJGNudCwgCiAgICAgICAgICAgICAgICAgbWV0aG9kID0gInhnYlRyZWUiLCB0ckNvbnRyb2wgPSB4Z2IuY3RyKQpwbG90KHhnYi53ZWVrKQpgYGAKCioqKioqCmBgYHtyfQp4Z2Iud2VlayRiZXN0VHVuZQpgYGAKCioqKioqCgoqIFJNU0UgPSBgciB3ZWVrLnhnYlssIG1lYW4oKGFjdHVhbCAtIHByZWQpXjIpXSAlPiUgc3FydGAKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CndlZWsueGdiID0gZGF0YS50YWJsZShob3VyW2R0ZWRheT49Y3V0b2ZmLCAuKGFjdHVhbCA9IGNudCwgZHRlZGF5KV0sCiAgICAgICAgICAgICAgICAgICAgICBwcmVkID0gcHJlZGljdCh4Z2Iud2VlaywgbmV3ZGF0YSA9IHdlZWsudGVzdCkpCnBsb3RfbHkod2Vlay54Z2IsIHggPSB+YWN0dWFsLCB5ID0gfnByZWQpICU+JQogIGFkZF9tYXJrZXJzKCkgJT4lCiAgYWRkX2xpbmVzKHg9YygxLDgwMCksIHk9YygxLDgwMCksIHNob3dsZWdlbmQgPSBGKQpgYGAKCioqKioqCgoqIEF2ZXJhZ2UgaG91cmx5IHByZWRpY3Rpb24KYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CkZlYXR1cmVEaXN0cmlidXRpb24obGlzdChUcmFpbiA9IGhvdXJbZHRlZGF5PGN1dG9mZiwuKGR0ZWRheSwgZ3JvdXAgPSAidHJhaW4iLCB2YWx1ZSA9IGNudCldLAogICAgICAgICAgICAgICAgICAgICAgICAgVGVzdCA9IHdlZWsueGdiWywuKGR0ZWRheSwgZ3JvdXAgPSAiYWN0dWFsIiwgdmFsdWUgPSBhY3R1YWwpXSwKICAgICAgICAgICAgICAgICAgICAgICAgIFByZWRzID0gd2Vlay54Z2JbLC4oZHRlZGF5LCBncm91cCA9ICJwcmVkIiwgdmFsdWUgPSBwcmVkKV0pLCAKICAgICAgICAgICAgICAgICAgICBmZWF0dXJlTmFtZSA9ICJ2YWx1ZSIsIHBlcmlvZE5hbWUgPSAiZHRlZGF5IikKYGBgCgoqKioqKgojIyMjIFhHQihkYXRlKQpgYGB7cn0Kc2V0LnNlZWQoc2VlZCkKeGdiLmRhdGUgPC0gdHJhaW4oeCA9IGRhdGUudHJhaW5bLC0iY250Il0sIHkgPSBkYXRlLnRyYWluJGNudCwgCiAgICAgICAgICAgICAgICAgbWV0aG9kID0gInhnYlRyZWUiLCB0ckNvbnRyb2wgPSB4Z2IuY3RyKQpwbG90KHhnYi5kYXRlKQpgYGAKCioqKioqCmBgYHtyfQp4Z2IuZGF0ZSRiZXN0VHVuZQpgYGAKCioqKioqCgoqIFJNU0UgPSBgciBkYXRlLnhnYlssIG1lYW4oKGFjdHVhbCAtIHByZWQpXjIpXSAlPiUgc3FydGAKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CmRhdGUueGdiID0gZGF0YS50YWJsZShob3VyW2R0ZWRheT49Y3V0b2ZmLCAuKGFjdHVhbCA9IGNudCwgZHRlZGF5KV0sCiAgICAgICAgICAgICAgICAgICAgICBwcmVkID0gcHJlZGljdCh4Z2IuZGF0ZSwgbmV3ZGF0YSA9IGRhdGUudGVzdCkpCnBsb3RfbHkoZGF0ZS54Z2IsIHggPSB+YWN0dWFsLCB5ID0gfnByZWQpICU+JQogIGFkZF9tYXJrZXJzKCkgJT4lCiAgYWRkX2xpbmVzKHg9YygxLDgwMCksIHk9YygxLDgwMCksIHNob3dsZWdlbmQgPSBGKQpgYGAKCioqKioqCgoqIEF2ZXJhZ2UgaG91cmx5IHByZWRpY3Rpb24KYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CkZlYXR1cmVEaXN0cmlidXRpb24obGlzdChUcmFpbiA9IGhvdXJbZHRlZGF5PGN1dG9mZiwuKGR0ZWRheSwgZ3JvdXAgPSAidHJhaW4iLCB2YWx1ZSA9IGNudCldLAogICAgICAgICAgICAgICAgICAgICAgICAgVGVzdCA9IGRhdGUueGdiWywuKGR0ZWRheSwgZ3JvdXAgPSAiYWN0dWFsIiwgdmFsdWUgPSBhY3R1YWwpXSwKICAgICAgICAgICAgICAgICAgICAgICAgIFByZWRzID0gZGF0ZS54Z2JbLC4oZHRlZGF5LCBncm91cCA9ICJwcmVkIiwgdmFsdWUgPSBwcmVkKV0pLCAKICAgICAgICAgICAgICAgICAgICBmZWF0dXJlTmFtZSA9ICJ2YWx1ZSIsIHBlcmlvZE5hbWUgPSAiZHRlZGF5IikKYGBgCgoqKioqKgojIyMjIFhHQihubyBtbnRoKQpgYGB7cn0Kc2V0LnNlZWQoc2VlZCkKeGdiLm10aCA8LSB0cmFpbih4ID0gZGF0YS50cmFpblssLWMoIm1udGgiLCJjbnQiKV0sIHkgPSBkYXRhLnRyYWluJGNudCwgCiAgICAgICAgICAgICAgICAgbWV0aG9kID0gInhnYlRyZWUiLCB0ckNvbnRyb2wgPSB4Z2IuY3RyKQpwbG90KHhnYi5tdGgpCmBgYAoKKioqKioKYGBge3J9CnhnYi5tdGgkYmVzdFR1bmUKYGBgCgoqKioqKgojIyMjIyBQZXJmb3JtYW5jZQoKKiBSTVNFID0gYHIgbXRoLnhnYlssIG1lYW4oKGFjdHVhbCAtIHByZWQpXjIpXSAlPiUgc3FydGAKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9Cm10aC54Z2IgPSBkYXRhLnRhYmxlKGhvdXJbZHRlZGF5Pj1jdXRvZmYsIC4oYWN0dWFsID0gY250LCBkdGVkYXkpXSwKICAgICAgICAgICAgICAgICAgICAgcHJlZCA9IHByZWRpY3QoeGdiLm10aCwgbmV3ZGF0YSA9IGRhdGEudGVzdCkpCnBsb3RfbHkobXRoLnhnYiwgeCA9IH5hY3R1YWwsIHkgPSB+cHJlZCkgJT4lCiAgYWRkX21hcmtlcnMoKSAlPiUKICBhZGRfbGluZXMoeD1jKDEsODAwKSwgeT1jKDEsODAwKSwgc2hvd2xlZ2VuZCA9IEYpCmBgYAoKKioqKioKKiBJbiBjb21wYXJpc29uLCBSTVNFIG9uIHRyYWluID0gYHIgbXRoLnhnYi50cmFpblssIG1lYW4oKGFjdHVhbCAtIHByZWQpXjIpXSAlPiUgc3FydGAKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9Cm10aC54Z2IudHJhaW4gPSBkYXRhLnRhYmxlKGFjdHVhbCA9IGRhdGEudHJhaW4kY250LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlZCA9IHByZWRpY3QoeGdiLm10aCwgbmV3ZGF0YSA9IGRhdGEudHJhaW4pKQpwbG90X2x5KG10aC54Z2IudHJhaW4sIHggPSB+YWN0dWFsLCB5ID0gfnByZWQpICU+JQogIGFkZF9tYXJrZXJzKCkgJT4lCiAgYWRkX2xpbmVzKHg9YygxLDgwMCksIHk9YygxLDgwMCksIHNob3dsZWdlbmQgPSBGKQpgYGAKCioqKioqCiMjIyBQZXJmb3JtYW5jZQoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CkZlYXR1cmVEaXN0cmlidXRpb24obGlzdChUcmFpbiA9IGhvdXJbZHRlZGF5PGN1dG9mZiwuKGR0ZWRheSwgZ3JvdXAgPSAidHJhaW4iLCB2YWx1ZSA9IGNudCldLAogICAgICAgICAgICAgICAgICAgICAgICAgVGVzdCA9IGRhdGUueGdiWywuKGR0ZWRheSwgZ3JvdXAgPSAiYWN0dWFsIiwgdmFsdWUgPSBhY3R1YWwpXSwKICAgICAgICAgICAgICAgICAgICAgICAgIE5vTXRoID0gbXRoLnhnYlssLihkdGVkYXksIGdyb3VwID0gInByZWQiLCB2YWx1ZSA9IHByZWQpXSwKICAgICAgICAgICAgICAgICAgICAgICAgIFhHQjIgPSBwcmVkLnR3b1ssLihkdGVkYXksIGdyb3VwID0gInByZWQiLCB2YWx1ZSA9IHByZWQpXSwKICAgICAgICAgICAgICAgICAgICAgICAgIFdlZWsgPSB3ZWVrLnhnYlssLihkdGVkYXksIGdyb3VwID0gInByZWQiLCB2YWx1ZSA9IHByZWQpXSwKICAgICAgICAgICAgICAgICAgICAgICAgIERhdGUgPSBkYXRlLnhnYlssLihkdGVkYXksIGdyb3VwID0gInByZWQiLCB2YWx1ZSA9IHByZWQpXSksIAogICAgICAgICAgICAgICAgICAgIGZlYXR1cmVOYW1lID0gInZhbHVlIiwgcGVyaW9kTmFtZSA9ICJkdGVkYXkiKQpgYGAKCioqKioqCiMjIEV4cGxhaW4KYGBge3J9CiNjYWxjLnJlbGltcApgYGAKCioqKioqCmBgYHtyLCBldmFsPUZBTFNFfQpzYXZlLmltYWdlKGZpbGUgPSAiYmlrZS5SRGF0YSIpCmxvYWQoImJpa2UuUkRhdGEiKQpgYGAK